﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats.Biff.Records;

namespace OpenLS.Spreadsheet.Formats.Biff.Tokens
{
    internal class FormulaTokenBuilder
    {
        private readonly AccessorType AccessType;
        private readonly Cell _cell;
        private readonly Cell _referenceCell;
        private readonly BiffWriterWorkbookContext _context;
        private readonly ParsedFormulaBase _formula;
        private readonly Stack<FormulaToken> _tokens = new Stack<FormulaToken>();

        public FormulaTokenBuilder(BiffWriterWorkbookContext context, AccessorType accessType, ParsedFormulaBase formula,
                                   Cell cell, Cell referenceCell)
        {
            Contract.Requires(context != null);
            _context = context;
            AccessType = accessType;
            _formula = formula;
            _cell = cell;
            _referenceCell = referenceCell;
        }

        public Stack<FormulaToken> Tokens
        {
            get
            {
                Contract.Ensures(Contract.Result<Stack<FormulaToken>>() != null);
                return _tokens;
            }
        }

        public void Apply(Expression expression)
        {
            Contract.Requires(expression != null);
            Contract.Assert(_context != null);
            if (expression is CellReferenceExpression)
            {
                var cellReferenceExpression = (CellReferenceExpression) expression;
                CellReference cell = cellReferenceExpression.CellReference;
                bool isFloating = cell.Row is FloatingRowReference && cell.Column is FloatingColumnReference;
                if (isFloating)
                {
                    int rowIndex = ((FloatingRowReference) cell.Row).RowOffset;
                    int columnIndex = ((FloatingColumnReference) cell.Column).ColumnOffset;
                    var token = new RefNToken((ushort) rowIndex,
                                              (short) columnIndex, !cell.Row.IsLocked,
                                              !cell.Column.IsLocked, AccessType);

                    if (cell.Worksheet == cellReferenceExpression.Caller)
                    {
                        Tokens.Push(token);
                        return;
                    }
                    throw new NotImplementedException();
                }
                if (this._referenceCell != null)
                {
                    var token = new RefNToken((ushort) (cell.Row.Anchor.Index - _referenceCell.Row.Index),
                                             (short) (cell.Column.Anchor.Index - _referenceCell.Column.Index), !cell.Row.IsLocked,
                                             !cell.Column.IsLocked, AccessType);
                    if (cell.Worksheet == cellReferenceExpression.Caller)
                    {
                        Tokens.Push(token);
                        return;
                    }
                    throw new NotImplementedException();
                }
                else
                {
                    var token = new RefToken((ushort) cell.Row.Anchor.Index,
                                             (short) cell.Column.Anchor.Index, !cell.Row.IsLocked,
                                             !cell.Column.IsLocked, AccessType);
                    if (cell.Worksheet == cellReferenceExpression.Caller)
                    {
                        Tokens.Push(token);
                        return;
                    }
                    Tokens.Push(new Ref3DToken(_context.GetSheetIndex(cell.Worksheet), token));
                }
                return;
            }
            if (expression is BinaryExpression)
            {
                var binaryExpression = (BinaryExpression) expression;
                Apply(binaryExpression.Left);
                Apply(binaryExpression.Right);
                Tokens.Push(new BinaryToken(binaryExpression.Operator));
                return;
            }
            if (expression is DoubleConstantExpression)
            {
                var doubleConstantExpression = (DoubleConstantExpression) expression;
                double value = doubleConstantExpression.DoubleValue;
                var i = (ushort) value;
                if (i == value)
                    Tokens.Push(new IntegerToken(i));
                else
                    Tokens.Push(new NumberToken(value));
                return;
            }
            if (expression is FunctionInvokeExpression)
            {
                var functionInvokeExpression = (FunctionInvokeExpression) expression;
                BuiltInFunctionId id;
                bool isCustom;
                try
                {
                    id =
                        (BuiltInFunctionId)
                        Enum.Parse(typeof (BuiltInFunctionId), functionInvokeExpression.Function.Name.ToUpper());
                    isCustom = false;
                }
                catch (Exception)
                {
                    isCustom = true;
                    id = BuiltInFunctionId.Custom;
                }
                //\\!(Enum.TryParse(functionInvokeExpression.Function.Name, true, out id));
                int argumentCount = functionInvokeExpression.Arguments.Count;
                if (functionInvokeExpression.Function.Name == "SUM" && argumentCount == 1)
                {
                    foreach (Expression arg in functionInvokeExpression.Arguments)
                        Apply(arg);
                    Tokens.Push(new AttributeToken(AttributeFlag.Sum));
                    return;
                }
                if (isCustom)
                {
                    Tokens.Push(new NameXToken(AccessorType.Reference, 0, 1));
                    id = BuiltInFunctionId.Custom;
                    argumentCount++;
                }
                foreach (Expression arg in functionInvokeExpression.Arguments)
                {
                    Apply(arg);
                }
                {
                    //   (BuiltInFunctionId) Enum.Parse(typeof (BuiltInFunctionId), functionInvokeExpression.Function.Name);
                    if (functionInvokeExpression.Function.UsesVar || isCustom)
                        Tokens.Push(new FunctionVarToken(AccessType, id, argumentCount));
                    else
                        Tokens.Push(new FunctionToken(AccessType, id));
                }
                return;
            }
            if (expression is ConcatenateExpression)
            {
                var concatenateExpression = (ConcatenateExpression) expression;
                Apply(concatenateExpression.Left);
                Apply(concatenateExpression.Right);
                Tokens.Push(new ConcatToken());
                return;
            }
            if (expression is StringConstantExpression)
            {
                var stringConstantExpression = (StringConstantExpression) expression;
                Tokens.Push(new StringConstantToken(stringConstantExpression.StringValue));
                return;
            }
            if (expression is PrefixUnaryExpression)
            {
                var prefixUnaryExpression = (PrefixUnaryExpression) expression;
                Apply(prefixUnaryExpression.Inner);
                Tokens.Push(new UnaryToken(prefixUnaryExpression.Operator));
                return;
            }
            if (expression is RangeReferenceExpression)
            {
                var rangeReferenceExpression = (RangeReferenceExpression) expression;
                CellReference topLeft;
                CellReference bottomRight;
                if (rangeReferenceExpression.RangeReference is MultiCellsRangeReference)
                {
                    var multiCellsRangeReference = (MultiCellsRangeReference) rangeReferenceExpression.RangeReference;
                    topLeft = multiCellsRangeReference.TopLeft;
                    bottomRight = multiCellsRangeReference.BottomRight;
                }
                else if (rangeReferenceExpression.RangeReference is FullColumnRangeReference)
                {
                    FullColumnRangeReference rr = (FullColumnRangeReference) rangeReferenceExpression.RangeReference;
                    topLeft = new StaticCellReference(new AnchoredRowReference(rr.Worksheet.Rows[0], false), new AnchoredColumnReference(rr.First.Anchor, ((ColumnReference)rr.First).IsLocked));
                    bottomRight = new StaticCellReference(new AnchoredRowReference(rr.Worksheet.Rows[Worksheet.MaxRowIndex], false), new AnchoredColumnReference(rr.Last.Anchor, ((ColumnReference)rr.Last).IsLocked));
                }
                else if (rangeReferenceExpression.RangeReference is FullRowRangeReference)
                {
                    FullRowRangeReference rr = (FullRowRangeReference) rangeReferenceExpression.RangeReference;
                    topLeft = new StaticCellReference(new AnchoredRowReference(rr.First.Anchor, ((RowReference)rr.First).IsLocked), new AnchoredColumnReference(rr.Worksheet.Columns[0], false));
                    bottomRight = new StaticCellReference(new AnchoredRowReference(rr.First.Anchor, ((RowReference)rr.Last).IsLocked), new AnchoredColumnReference(rr.Worksheet.Columns[Worksheet.MaxColumnIndex], false));
                }
                else
                {
                    throw new NotImplementedException();
                }
                if (rangeReferenceExpression.Worksheet == rangeReferenceExpression.Caller)
                {
                    if (_formula is SharedParsedFormula)
                    {
                        var areaNToken = new AreaNToken(AccessorType.Reference,
                                                        new RgceAreaRel(
                                                            new RgceLocRel(
                                                                (ushort)
                                                                (topLeft.Row.Anchor.Index - _cell.Row.Index),
                                                                (short)
                                                                (topLeft.Column.Anchor.Index - _cell.Column.Index),
                                                                !topLeft.Row.IsLocked, !topLeft.Column.IsLocked),
                                                            new RgceLocRel(
                                                                (ushort)
                                                                (bottomRight.Row.Anchor.Index - _cell.Row.Index),
                                                                (short)
                                                                (bottomRight.Column.Anchor.Index -
                                                                 _cell.Column.Index), !bottomRight.Row.IsLocked,
                                                                !bottomRight.Column.IsLocked)
                                                            ));
                        Tokens.Push(areaNToken);
                        return;
                    }
                    var areaToken = new AreaToken(AccessorType.Reference,
                                                  new RgceArea((ushort) topLeft.Row.Anchor.Index,
                                                               (ushort) topLeft.Column.Anchor.Index,
                                                               !topLeft.Row.IsLocked, !topLeft.Column.IsLocked,
                                                               (ushort) bottomRight.Row.Anchor.Index,
                                                               (ushort) bottomRight.Column.Anchor.Index,
                                                               !bottomRight.Row.IsLocked,
                                                               !bottomRight.Column.IsLocked), _formula);
                    Tokens.Push(areaToken);
                    return;
                }
                Tokens.Push(new Area3DToken(AccessorType.Reference,
                                            _context.GetSheetIndex(rangeReferenceExpression.RangeReference.Worksheet),
                                            new RangeReferenceAddress(
                                                new CellReferenceAddress(topLeft),
                                                new CellReferenceAddress(bottomRight))));
                return;
            }
            if (expression is NameExpression)
            {
                var nameExpression = (NameExpression) expression;
                DefinedName definedName = nameExpression.GetDefinedName();
                if (definedName != null)
                    Tokens.Push(new NameToken(AccessType, _context.GetDefinedNameId(definedName)));
                else
                {
                    Tokens.Push((new NameToken(AccessType, _context.GetDefinedNameId(nameExpression.Name    ))));
                }
                return;
            }
            if (expression is ParenthesizedExpression)
            {
                var parenthesizedExpression = (ParenthesizedExpression) expression;
                Apply(parenthesizedExpression.Inner);
                Tokens.Push(new ParenthesesToken());
                return;
            }
            if (expression is ArrayExpression)
            {
                var arrayExpression = (ArrayExpression) expression;
                Tokens.Push(new ArrayToken(AccessorType.Array, Enumerable.Select(arrayExpression.Expressions, e =>
                                                                                                              Enumerable
                                                                                                                  .
                                                                                                                  Select
                                                                                                                  (e,
                                                                                                                   ee =>
                                                                                                                   ((
                                                                                                                    ConstantExpression
                                                                                                                    ) ee)
                                                                                                                       .
                                                                                                                       Value
                                                                                                                  ))));
                return;
            }
            if (expression is PercentExpression)
            {
                var percentExpression = (PercentExpression) expression;
                Apply(percentExpression.Inner);
                Tokens.Push(new PercentToken());
                return;
            }
            if (expression is ErrorExpression)
            {
                var errorExpression = (ErrorExpression) expression;
                Tokens.Push(new ErrorToken(errorExpression.Error.Type));
                return;
            }
            if (expression is SharedExpressionReference)
            {
                var sharedExpressionReference = (SharedExpressionReference) expression;
                CellAddress topLeft = sharedExpressionReference.SharedExpression.Address.TopLeft;

                var token = new ArrayFormulaReferenceToken((ushort) topLeft.Row, (ushort) topLeft.Column);
                Tokens.Push(token);
                return;
            }
            if (expression is BooleanConstantExpression)
            {
                BooleanConstantExpression booleanConstantExpression = (BooleanConstantExpression) expression;
                BooleanToken booleanToken = new BooleanToken(booleanConstantExpression.BooleanValue);
                Tokens.Push(booleanToken);
                return;

            }
            throw new NotImplementedException(expression.GetType().Name);
        }
    }
}